#!/usr/sbin/rsct/perl5/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2001,2004 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
#"@(#)53   1.16   src/rsct/rm/ConfigRM/cli/bin/preprpnode.perl, configrmcli, rsct_rzauh, rzauh0431a 5/24/04 14:51:01"
######################################################################
#                                                                    #
# Module: preprpnode                                                 #
#                                                                    #
# Purpose:                                                           #
#   preprpnode - prepares a node to join an RSCT peer domain.        #
#                                                                    #
# Syntax:                                                            #
#   preprpnode [-h] [-k] [-TV] Node_name [Node_name ...]             #
#                                                                    #
#   preprpnode [-h] [-k] -f File | -F File  [-TV]                    #
#                                                                    #
# Flags:                                                             #
#   -h      Help. Writes the command's usage statement to standard   #
#           output.                                                  #
#   -f File The file containing the nodes names to be added to the   #
#           peer domain. The node names are defined as Node_name     #
#           operands or in a file, not both.  When in a file, each   #
#           line of the file is scanned for one node name.  Comments #
#           may be placed on each line, but after the comment        #
#           character "#". Lines that start (col 1) with "#" or are  #
#           entirely blank are ignored.  Use -f "-" to specify STDIN #
#           as the input file.                                       #
#   -F File Same as -f.                                              #
#   -k      Do not exchange public keys.                             #
#   -T      Trace. Writes the command's trace messages to standard   #
#           error. For your software-service organization's use only.#
#   -V      Verbose. Writes the command's verbose messages to        #
#           standard output.                                         #
#                                                                    #
# Operands:                                                          #
#   Node_name   The node to prepare to join the peer domain.         #
#                                                                    #
# Description:                                                       #
#   The preprpnode command performs the proper actions to set up a   #
#   peer domain.                                                     #
#                                                                    #
#                                                                    #
#                                                                    #
# Exit Values:                                                       #
#   0  CRM_CLI_SUCCESS       Command completed successfully.         #
#   1  CRM_CLI_RMC_ERROR     Command terminated due to an underlying #
#                            RMC error.                              #
#   2  CRM_CLI_ERROR         Command terminated due to an underlying #
#                            error in the command script.            #
#   3  CRM_CLI_BAD_FLAG      Command terminated due to user          #
#                            specifying an invalid flag.             #
#   4  CRM_CLI_BAD_OPERAND   Command terminated due to user          #
#                            specifying a bad operand.               #
#   5  CRM_CLI_USER_ERROR    Command terminated due to a user error, #
#                            for example specifying a name that      #
#                            already exists.                         #
#                                                                    #
# Examples:                                                          #
#   1. To prepare a node to join a peer domain, run:                 #
#      preprpnode abc.def.com                                        #
#                                                                    #
# Man Page:                                                          #
#   For the most current detailed description of this command see    #
#   the preprpnode man page in /usr/sbin/rsct/man.                   #
#                                                                    #
#--------------------------------------------------------------------#
# Inputs:                                                            #
#   /usr/sbin/rsct/msgmaps/configrmcli.preprpnode.map -              # 
#       message mapping                                              #
#                                                                    #
# Outputs:                                                           #
#   stdout - none.                                                   #
#   stderr - any error message.                                      #
#                                                                    #
# External Ref:                                                      #
#   Commands: ctdspmsg                                               #
#   Modules:  CRM_cli_utils.pm, CRM_cli_rc.pm,                       #
#             CRM_cli_include.pm, CT_cli_utils.pm                    #
#   Perl library routines: Getopt::Std                               #
#                                                                    #
# Tab Settings:                                                      #
#   4 and tabs should be expanded to spaces before saving this file. #
#   in vi:  (:set ts=4  and   :%!expand -4)                          #
#                                                                    #
# Change Activity:                                                   #
#   011225 JAC 77313: Initial design & write.                        #
#   020320 JAC 81275: Use lsrsrc-api and rework some acls updates.   #
#   020422 JAC 82248: Rename prepclnode to preprpnode.               #
#   020422 JAC 82249: Change cluster resource class name.            #
#   020428 JAC 82316: Call process_exit_code to check $rc.           #
#   020506 JAC 82449: Add -f flag and change acls processing.        #
#   020621 JAC 83646: Change rmcctrl rc check for only rc.           #
#   021205 JAC 89447: Put both name and IP addr in THL and ACLs.     #
#   030307 JAC 92297: Continue to next node instead of exit on error.#
#   030728 JAC 94796: Redo some ACL file processing due to           #
#                     corruption problems.                           #
#   030908 JAC 96488: Get Hostname from HostPublic class and add     #
#                     to rmc acls.                                   #
#   030929 JAC 99773: Add IPaddr of Hostname to rmcacls.             #
#   040319 JAC 105987: Add -F flag to do the same as -f.             #
#   040406 JAC 106712: Update usage for file options.                #
#   040502 JAC 108124: Add Hostname and it's IP addr to THL.         #
#   040524 JAC 101261: Use error msg with node name when lsrsrc fails#
######################################################################

#--------------------------------------------------------------------#
# General Program Flow/Logic:                                        #
#                                                                    #
# 1. Determine if public keys need to be exchanged (no -k)           #
# 2. Enable remote connections for rmc (rmcctrl -p)                  #
# 3. For each node,                                                  #
#    a. retrieve its public key (lsrsrc on IBM.HostPublic)           #   
#    b. add it to trusted host list (ctsthl cmd)                     #
#    c. add root@node to rmc acls giving it rw permission to         #
#       IBM.SharedResourceCluster                                    #
# 4. Return back any errors.                                         #
#                                                                    #
#--------------------------------------------------------------------#

#--------------------------------------------------------------------#
# Included Libraries and Extensions                                  #
#--------------------------------------------------------------------#
use lib "/usr/sbin/rsct/pm";
use locale;
use Getopt::Std;

use CT_cli_utils qw(printIMsg
                    printEMsg);

use CRM_cli_rc qw(CRM_CLI_SUCCESS CRM_CLI_RMC_ERROR
                  CRM_CLI_ERROR CRM_CLI_BAD_FLAG
                  CRM_CLI_BAD_OPERAND CRM_CLI_USER_ERROR);
use CRM_cli_utils qw(error_exit
                     printCIMsg
                     printCEMsg
                     resolve_node_names_ipaddrs
                     process_api_error
                     process_exit_code
                     remove_api_error
                     get_nodes_nums_from_file
                     get_opstate_by_name);
use CRM_cli_include qw($TRUE $FALSE
                       $RSCLUSTER $RMC_CLI_USER_ERROR
                       $DELIMITER
                       $CTBINDIR $CTDIR);

#--------------------------------------------------------------------#
# Global Variables                                                   #
#--------------------------------------------------------------------#
$Trace = $FALSE;                        # default - trace off
$Verbose = $FALSE;                      # default - verbose turned off

$Opt_NoKeys = $FALSE;                   # default - skip key exchange
$Opt_File_Input = $FALSE;               # default - no file

$PROGNAME = "preprpnode";               # Program Name for messages
$LSMSG = "$CTBINDIR/ctdspmsg";          # list / display message rtn
$MSGCAT = "configrmcli.cat";            # msg catalogue for this cmd
$ENV{'MSGMAPPATH'} = "$CTDIR/msgmaps";  # msg maps used by $LSMSG

#--------------------------------------------------------------------#
# Variables                                                          #
#--------------------------------------------------------------------#
my @node_names = ();                    # nodes in cluster
my $one_node = "";                      # one from nodelist
my $resolved_node_names = "";           # resolved node names
my $DNS_or_IP_names = "";               # DNS/IP hash reference 
my $unresolved_node_names = "";         # unresolved node names
my $resolved_hostnames = "";            # resolved Hostnames
my $DNS_or_IP_of_hostnames = "";        # DNS/IP hash for Hostnames 
my $unresolved_hostnames = "";          # unresolved Hostnames
my $resolved_node_list = "";            # resolved node list
my @lsr_out = ();                       # output from lsrsrc to get key
my @cmd_out = ();                       # output from a command
my $method = "";                        # trusted key method
my $keyvalue = "";                      # trusted key value
my $acls_dflt = "/usr/sbin/rsct/cfg/ctrmc.acls"; # default acls
my $acls_file = "/var/ct/cfg/ctrmc.acls"; # rmc acls file
my $acls_old = "/var/ct/cfg/ctrmc.acls.preprpnode.old"; # old version
my $acls_new = "/var/ct/cfg/ctrmc.acls.preprpnode.new"; # new version
my $acls_lck = "/var/ct/lck/ctrmc.acls.lock"; # lock name 
my $file_name = "";                     # input file name
my $node_names_file = "";               # array reference
my $node_nums_file = "";                # array reference
my @dns_or_ip_names = ();               # dns or IP names 
my $one_dns_or_ip_name = "";            # one from dns_or_ip_names
my $pubkey = "";                        # public key from HostPublic
my $hostname = "";                      # hostname from HostPublic
my @Lhostnames = ();                    # list of hostnames
my @Lhostnames_unique = ();             # list of new hostnames
my @THL_names = ();                     # list to put in THL
my $i = 0;
my $j = 0;
my $found = $FALSE;

my $passopts = "";                      # TV options to pass to RMC CLI
my $other_opts = "";                    # parameters to pass to RMC CLI
#--------------------------------------------------------------------#
# Main Code                                                          #
#--------------------------------------------------------------------#
my $rc = 0;
my $config_rc = 0;

# parse the command line, exit if there are errors 
($rc, $file_name, @node_names) = &parse_cmd_line;
($rc == 0) || error_exit($rc);

if ($Verbose) { printIMsg("IMsgpreprpnodeStart"); }

if ($Trace) { $passopts = $passopts." -T"; }
if ($Verbose) { $passopts = $passopts." -V"; }

# get the node names and numbers from a file, if specified
if ($Opt_File_Input) {

   # extract the node names and numbers from the file
   ($node_names_file, $node_nums_file) = get_nodes_nums_from_file($file_name);

   # copy to other array (don't care about node numbers)
   @node_names = @$node_names_file;
}

# enable remote rmc connections
if ($Trace) { print STDERR "$PROGNAME: calling rmcctrl\n";}

@cmd_out = `$CTBINDIR/rmcctrl -p 2>&1`;

# capture the return code from rmcctrl
$rc = $?;
$rc = process_exit_code($rc);

if ($Trace) { print STDERR "$PROGNAME: rmcctrl returned $rc\n";}

# process any errors from rmcctrl
if ($rc != 0) {
   print STDERR @cmd_out;
   printEMsg("EMsgpreprpnodermcctrlErr",$rc);
   exit(CRM_CLI_RMC_ERROR);
}

# rmcctrl ok but check for output
else {
   if ($#cmd_out >= 0) { 
      print @cmd_out;
   }
}

# resolve the node names 
# (rmc acls format requires it so might as well get it over with)
($resolved_node_names, $DNS_or_IP_names, $unresolved_node_names) = 
       resolve_node_names_ipaddrs(@node_names);

# write an error message for each unresolved node name found
# continue processes but set rc
foreach $one_node (@$unresolved_node_names) {
   printEMsg("EMsgpreprpnodeUnresolvedNode",$one_node);
   $config_rc = CRM_CLI_USER_ERROR;
}

# exit if there are no node names left to process
if ($#$resolved_node_names < 0) { exit($config_rc); } 

# exchange public key, if required (which is the default)
if (!$Opt_NoKeys) {

   # set the security to unauthenticated
   $ENV{CT_SEC_MECH} = "none";

   # set the rmc management scope to local
   $ENV{CT_MANAGEMENT_SCOPE} = 1;

   # for each node passed to preprpnode, get its public key
   # and add it to the local node's trusted host list
   foreach $one_node (@$resolved_node_names) {

      # get its public key
      # set CT_CONTACT to access remote node
      $ENV{CT_CONTACT} = $one_node;

      # run lsrsrc to get the public key from the remote node
      if ($Trace) { print STDERR "$PROGNAME: calling lsrsrc-api for $one_node\n";}

#     @lsr_out = `$CTBINDIR/lsrsrc -x -d $passopts IBM.HostPublic PublicKey`;
#     @lsr_out = `$CTBINDIR/lsrsrc-api -D $DELIMITER -o IBM.HostPublic::::::PublicKey 2>&1`;
      @lsr_out = `$CTBINDIR/lsrsrc-api -D $DELIMITER -o IBM.HostPublic::::::PublicKey::Hostname 2>&1`;

      # capture the return code from lsrsrc
      $rc = $?;
      $rc = process_exit_code($rc);
    
      if ($Trace) { print STDERR "lsrsrc-api results:\n";
                    print STDERR "@lsr_out";}

      if ($Trace) { print STDERR "$PROGNAME: lsrsrc-api returned $rc\n";}

      # remove any Hostname attribute not found error 
      ($rc, @lsr_out) = &remove_hostname_not_found($rc, $DELIMITER, @lsr_out);

      # show any errors if there was a bad rc
      if ($rc != 0)  {
         process_api_error($DELIMITER,@lsr_out);
         printEMsg("EMsgpreprpnodeKeyUnavail",$one_node);
      }

      # return ConfigRM CLI user error if it's an RMC CLI user error
      if ($rc == $RMC_CLI_USER_ERROR) { 
         #exit(CRM_CLI_USER_ERROR);
         $config_rc = CRM_CLI_USER_ERROR;
         next;
      }

      # if lsrsrc failed for something else, print RMC error message and exit
      if ($rc != 0) {
         #exit(CRM_CLI_RMC_ERROR);
         $config_rc = CRM_CLI_RMC_ERROR;
         next;
      }

      # split the output into the public key and the hostname
      ($pubkey, $hostname) = split /$DELIMITER/, $lsr_out[0];
      chomp($pubkey);
      chomp($hostname);

      # clear out list of node to put in THL to rebuild it
      @THL_names = ();

      # if there's a hostname, save it to add to acls
      if ($hostname !~ /^$/ ) {
         $hostname =~ s/\"//g;
         push (@Lhostnames,$hostname);
         push (@THL_names, $hostname);
      }

      # get the IP address of the Hostname from IBM.HostPublic
      ($resolved_hostnames, $DNS_or_IP_of_hostnames, $unresolved_hostnames) =
          resolve_node_names_ipaddrs($hostname);

      # add the IP addrs to the hostname list and THL list
      @dns_or_ip_names = keys %$DNS_or_IP_of_hostnames;
      foreach $one_dns_or_ip_name (@dns_or_ip_names) {
          push (@Lhostnames,$$DNS_or_IP_of_hostnames{$one_dns_or_ip_name});
          push (@THL_names,$$DNS_or_IP_of_hostnames{$one_dns_or_ip_name});
      }

      # add the remote node to THL
      push (@THL_names, $one_node);

      # if it's an IP addr, add the DNS name too, and vice versa
      if (defined $$DNS_or_IP_names{$one_node}) {

         push (@THL_names, $$DNS_or_IP_names{$one_node});

      }

      # update THL with the list of names/IP addrs in @THL_names
      $rc = &add_host_to_thl($pubkey, @THL_names);

      # set config_rc if it's not already set
      if ($config_rc == 0) { $config_rc = $rc; }

   }  #end foreach to exchange key

}  # end if exchange keys required

# Update ACLs

# obtain lock on lock file
open (LOCKFH, ">$acls_lck");
flock (LOCKFH,2);

# create temporary file to use for new acls file
# it's either the current acl file or the template file
if (-e "$acls_file") {
   # create temporary new acls file from existing file 
   @cmd_out = `/bin/cp -p $acls_file $acls_new 2>&1`;
   $rc = $?;
}

else {
   # create temporary new acls file from template 
   @cmd_out = `/bin/cp -p $acls_dflt $acls_new 2>&1`;
   $rc = $?;
}

# check cp return code
$rc = process_exit_code($rc);
if ($rc != 0) {
   print STDERR @cmd_out;
   printEMsg("EMsgpreprpnodeAclsProb");
   close (LOCKFH);
   exit(CRM_CLI_USER_ERROR);
}

# keep old acl file (maybe this is temporary)
@cmd_out = `/bin/cp -p $acls_new $acls_old 2>&1`;
$rc = $?;
$rc = process_exit_code($rc);

# process any copy errors 
if ($rc != 0) {
   print STDERR @cmd_out;
   printEMsg("EMsgpreprpnodeAclsProb");
   # delete new tempory file that was created
   @cmd_out = `/bin/rm $acls_new 2>&1`;
   close (LOCKFH);
   exit(CRM_CLI_USER_ERROR);
}

# update the rmc acls file
# use all resolved nodes plus any additional DNS names
@node_names = @$resolved_node_names;
@dns_or_ip_names = keys %$DNS_or_IP_names;

foreach $one_dns_or_ip_name (@dns_or_ip_names) {
   push (@node_names,$$DNS_or_IP_names{$one_dns_or_ip_name});
}

# add any hostnames found from HostPublic class to @node_names
# but first make sure they're not already on the list
for ($i=0; $i<=$#Lhostnames; $i++) {
   $found = $FALSE;
   for ($j=0; (($j<=$#node_names) && !$found); $j++) {
      if ($Lhostnames[$i] eq $node_names[$j]) {
         $found = $TRUE;
      }
   }
   if (!$found) {
      push(@Lhostnames_unique, $Lhostnames[$i]);
   }
}
   
# add the unique hostnames found from HostPublic class
foreach $hostname (@Lhostnames_unique) {
   push (@node_names,$hostname);
}

&update_acls_file ($acls_new, $acls_file, @node_names);

# refresh RMC to read in the new acls
if ($Trace) { print STDERR "$PROGNAME: calling refresh\n";}
@cmd_out = `refresh -s ctrmc 2>&1`;
$rc = $?;
$rc = process_exit_code($rc);

# unlock the lock file
close (LOCKFH);

if ($Trace) { print STDERR "$PROGNAME: refresh returned $rc\n";}
# if it failed, print error message and exit
if ($rc != 0) {
   print STDERR @cmd_out;
   printEMsg("EMsgpreprpnodeRefrErr");
   exit(CRM_CLI_RMC_ERROR);
}

if ($Verbose) { printIMsg("IMsgpreprpnodeEnd"); }

if ($config_rc == 0) { exit($rc); }
else { exit($config_rc); } 

#--------------------------------------------------------------------#
# End Main Code                                                      #
#--------------------------------------------------------------------#


#--------------------------------------------------------------------#
# parse_cmd_line - Parse the command line for options and operands.  #
#   Set appropriate global variables as outlined below, make sure we #
#   have a valid combination of arguments / options.                 #
#                                                                    #
# Return:                                                            #
#   $rc   0                  Command line parsed fine, no problem.   #
#         CRM_CLI_BAD_FLAG   Command line contained a bad flag.      #
#   @node_names              Nodes to belong to the cluster          #
#                                                                    #
# Global Variables Modified:                                         #
#   $Verbose           output   True (-V) turn Verbose mode on.      #
#   $Trace             output   True (-T) turn Trace mode on.        #
#   $Opt_File_Input    output   True (-f) file name specified        #
#   $Opt_NoKeys        output   True (-k) no keys exchanged.         #
#--------------------------------------------------------------------#
sub parse_cmd_line 
{
my(@original_argv) = @ARGV;
my @node_names = ();                    # node names
my $file_name = "";
my %opts = ();

# Process the command line...
if (!&getopts('hf:F:kVT', \%opts)) {    # Gather options; 
                                        # if errors
    &print_usage;                       # display proper usage
    return CRM_CLI_BAD_FLAG;            # return bad rc - bad flag 
}

# process h flag
if (defined $opts{h}) {                 # -h, help request
    &print_usage;                       # print usage statement
    exit(0);                            # all done with good return!
}

if (defined $opts{T}) {                 # -T turn trace on
    $Trace = $TRUE;
}

if (defined $opts{V}) {                 # -V turn verbose mode on
    $Verbose = $TRUE;
}

# Get the arguments...
# Operands:  one or more node names
if ($#ARGV >= 0) {                      # at least one node name
   @node_names = @ARGV;                 # get node names
}

# make sure -f for file used if there are no node names
if (($#node_names < 0) && !(defined $opts{f}||defined $opts{F})) {
    printCEMsg("EMsgConfigRMcliInvalidNumberOfOperands");
    &print_usage;
    return CRM_CLI_BAD_OPERAND;
}

if (defined $opts{f}) {                 # -f for file

    # make sure both -f and -F not used together
    if (defined $opts{F}) {
       printCEMsg("EMsgConfigRMcliImproperUsageCombination","-f","-F");
       &print_usage;
       return CRM_CLI_BAD_FLAG;
   }
    $Opt_File_Input = $TRUE;
    $file_name = $opts{f};
    # make sure file and cmd line not both used for node names
    if ($#node_names >= 0) {
       printCEMsg("EMsgConfigRMcliInvalidNumberOfOperands");
       &print_usage;
       return CRM_CLI_BAD_OPERAND;
   }
}

if (defined $opts{F}) {                 # -F for file
    $Opt_File_Input = $TRUE;
    $file_name = $opts{F};
    # make sure file and cmd line not both used for node names
    if ($#node_names >= 0) {
       printCEMsg("EMsgConfigRMcliInvalidNumberOfOperands");
       &print_usage;
       return CRM_CLI_BAD_OPERAND;
   }
}

if (defined $opts{k}) {                 # -k for no key exchange
    $Opt_NoKeys = $TRUE;                # -k flag specified
}

return(0, $file_name, @node_names);     # success

}   # end parse_cmd_line


#--------------------------------------------------------------------#
# print_usage : print the usage statement (syntax) to stdout.        #
#--------------------------------------------------------------------#
sub print_usage
{
&printIMsg("IMsgpreprpnodeUsageF");
}   # end print_usage


#--------------------------------------------------------------------#
# add_host_to_thl:                                                   #
#     Adds the hosts included in the call to the THL file. The       #
#     inputs are the public key attribute (from IBM.HostPublic) and  #
#     the list of host names or IP addrs to use as the -n host for   #
#     the ctsthl command. ctsthl is run one or more times, once for  #
#     each host name or IP address.                                  #
#                                                                    #
# Paramaters:                                                        #
#   $pubkey         The public key attribute from IBM.HostPublic.    #
#   @thl_hosts      The list of host names/IP adds to add to THL.    #
#                                                                    #
# Returns:                                                           #
#   $configrc       =0 if all adds are successful.                   #
#                   !0 if any errors.                                #
#                                                                    #
#--------------------------------------------------------------------#
sub add_host_to_thl
{
my $pubkey = shift(@_);
my @thl_hosts = @_;

my $method = "";                        # trusted key method
my $keyvalue = "";                      # trusted key value
my $one_host = "";                      # a single host/IP addr
my @cmd_out = ();                       # output from ctsthl
my $rc = 0;                             # initialize rc
my $configrc = 0;                       # initialize rc to return

# parse the public key which is an SD of method and key
($method, $keyvalue) = split /,/, $pubkey;

# do some cleaning up of the punctuation around the values
$method =~ s/\"//g;
$method =~ s/\[//g;
$method =~ s/\]//g;
$method =~ s/://g;
$keyvalue =~ s/\"//g;
$keyvalue =~ s/\[//g;
$keyvalue =~ s/\]//g;
$keyvalue =~ s/://g;

# loop through each host/IP addr and add it to THL
foreach $one_host (@thl_hosts) {

    # add this host/IP addr to local node's trusted host list
    if ($Trace) { print STDERR "$PROGNAME: calling ctsthl for $one_host\n";}

    @cmd_out = `$CTBINDIR/ctsthl -a -n $one_host -m $method -p $keyvalue 2>&1`;

    # capture the return code from ctsthl
    $rc = $?;
    $rc = process_exit_code($rc);

    if ($Trace) { print STDERR "$PROGNAME: ctsthl returned $rc\n";}

    # process any errors from ctsthl
    if ($rc != 0) {
       print STDERR @cmd_out;
       printEMsg("EMsgpreprpnodectsthlErr",$one_host,$rc);
       #exit(CRM_CLI_RMC_ERROR);
       $configrc = CRM_CLI_RMC_ERROR;
       next;
    }
}

return ($configrc);
}   # end add_host_to_thl


#--------------------------------------------------------------------#
# update_acls_file - Make updates to the acls file for configrm.     #
#      Update the rmc acls file specified by $file_name.  Use the    #
#      @node_names array to give access to root at these nodes.      #
#                                                                    #
# Input:                                                             #
#   $file_name               The name of the temporary new acls file.#
#   $acl_file                The name of the acls file.              #
#   @node_names              The nodes to be used for preprpnode.    #
#                                                                    #
# Returns:                                                           #
#   None.                                                            #
#--------------------------------------------------------------------#
sub update_acls_file
{

my $acl_line = "";                        # line from file
my $i = -1;                               # acl file counter
my @acl_array = ();                       # acls in array
my %stanzas = ();                         # stanzas hash
my $stanza_name = "";                     # current stanza
my $junk = "";                            # junk
my $myline = "";
my $stanza_found = $FALSE;                # no stanzas
my $stanza_line_found = $FALSE;           # no stanzas
my $file_error = "";                      # when file doesn't open
my $node_name = "";
my $ii = 0;
my $jj = 0;
my $linenum;
my $original_acl_count = 0;
my $endrscluster = "ENDxxx".$RSCLUSTER;
my @cmd_out = ();

# get the file name passed in
my $file_name = shift @_;
my $acl_file = shift @_;
my @node_names = @_;

# open the file. process if it opens ok
if ( open (ACLFILE,"$file_name") ) {

   # read until eof
   while (<ACLFILE>) {

      # get a line from the file
      $acl_line = $_;

      # get rid of any new line characters
      chomp($acl_line);

      # add it to array
      $acl_array[++$i] = $acl_line;

   }  # end of "while not eof" loop

   # close the file
   close(ACLFILE);
 
   # save original size of acls file to see later if its grown
   $original_acl_count = $#acl_array;

   # form the acl hash of stanza names
   %stanzas = &find_stanzas(@acl_array);

   # if IBM.PeerDomain was not found, add it
   if (!(defined $stanzas{$RSCLUSTER})) {

      $acl_array[++$i] = "";
      $acl_array[++$i] = "$RSCLUSTER";
      $acl_array[++$i] = "    none:root      *   rw   // root on any node of active cluster";
      $acl_array[++$i] = "    none:any_root  *   rw   // root on any node of any cluster that this node is defined to";

      # add the node names
      foreach $node_name (@node_names) {
         $acl_array[++$i] = "    root\@$node_name *   rw   // cluster node";
      }

   }
   else {
      # see what to do
      # look through IBM.PeerDomain's lines
      $ii = $stanzas{$RSCLUSTER};
      $jj = $stanzas{$endrscluster};

      # "none:root * rw" and "none:any_root * rw" need to be there in this
      # order.  This doesn't change it if they're already out of order

      # see if the stanza line "none:root * rw" exists
      $linenum = &find_stanza_line("    none:root   *  rw  // root on any node of active cluster",$ii,$jj,@acl_array);

      # if it's not found, figure out where to add it
      if ($linenum < 0) {
      
          # see if the stanza line "none:any_root * rw" exists
          $linenum = &find_stanza_line("    none:any_root * rw  // root on any node of any cluster where local node defined",$ii,$jj,@acl_array);
       
          # if it's found, add the 1st stanza line before the second
          if ($linenum >= 0) {
             @acl_array = &insert_in_array("    none:root   *  rw  // root on any node of active cluster",$linenum-1,@acl_array);
          }
          # otherwise, add it to the end of the stanza
          else {
             @acl_array = &add_stanza_line("    none:root   *  rw  // root on any node of active cluster",$ii,$jj,@acl_array);
          }
      }

      # refresh the stanza hash mapping
      %stanzas = ();
      %stanzas = &find_stanzas(@acl_array);

      # reset the stanza indexes
      $ii = $stanzas{$RSCLUSTER};
      $jj = $stanzas{$endrscluster};

      # see if the stanza line "none:any_root * rw" exists
      $linenum = &find_stanza_line("    none:any_root * rw  // root on any node of any cluster where local node defined",$ii,$jj,@acl_array);

      # if it's not found, add it after the 1st line (which must now exist)
      if ($linenum < 0) {
         
         # find the 1st line
         $linenum = &find_stanza_line("    none:root   *  rw  // root on any node of active cluster",$ii,$jj,@acl_array);

         # add the 2nd line after the first
         @acl_array = &insert_in_array("    none:any_root * rw  // root on any node of any cluster where local node defined",$linenum,@acl_array);
      }

      # refresh the stanza hash mapping
      %stanzas = ();
      %stanzas = &find_stanzas(@acl_array);

      # add the node names
      foreach $node_name (@node_names) {

         # reset the stanza indexes
         $ii = $stanzas{$RSCLUSTER};
         $jj = $stanzas{$endrscluster};

         # if "root@$node_name * rw" isn't there, add it
         @acl_array = &add_stanza_line("    root\@$node_name *   rw   // cluster node",$ii,$jj,@acl_array);

         # refresh the stanza hash mapping
         %stanzas = ();
         %stanzas = &find_stanzas(@acl_array);
      }
   }

   # refresh the stanza hash mapping
   %stanzas = ();
   %stanzas = &find_stanzas(@acl_array);

   # was DEFAULT not found, add it
   if (!(defined $stanzas{DEFAULT})) {
      $acl_array[++$i] = "";
      $acl_array[++$i] = "DEFAULT";
      $acl_array[++$i] = "     root\@LOCALHOST   *   rw   // default authority for root";
      $acl_array[++$i] = "     LOCALHOST         *   r    // default authority for other user";
      $acl_array[++$i] = "     none:root        *   rw   // give root on any node access to all";

   }
   else {
      # look through DEFAULT's lines 
      $ii = $stanzas{DEFAULT};
      $jj = $stanzas{ENDxxxDEFAULT};

      # if "none:root * rw" isn't there, add it
      @acl_array = &add_stanza_line("    none:root   *  rw  // give root access to all",$ii,$jj,@acl_array);

   }

   # write out new acls file if its changed
   if ($original_acl_count != $#acl_array) {
   
      # open the file for writing. process if it opens ok
      if ( open (ACLFILE,">$file_name") ) {
         
         # write each line
         foreach $acl_line (@acl_array) {
            print ACLFILE $acl_line,"\n";
         }
         close(ACLFILE);
      }
      # else open for write failed
      else {
         # get the error
         $file_error = $!;

         # print error message and exit
         &printEMsg("EMsgpreprpnodeAclsFileError",$file_name,$file_error);
         # delete new tempory file that was created
         @cmd_out = `/bin/rm $file_name 2>&1`;
         close (LOCKFH);
         exit(CRM_CLI_USER_ERROR);
      }

      # rename the temporary file to the actual name
      if (! (rename $file_name, $acl_file)){
         # get the error
         $file_error = $!;

         # print error message and exit
         &printEMsg("EMsgpreprpnodeAclsFileError",$file_name,$file_error);
         # delete new tempory file that was created
         @cmd_out = `/bin/rm $file_name 2>&1`;
         close (LOCKFH);
         exit(CRM_CLI_USER_ERROR);
      }
   }
   # else count is the same, no change to acl file occurred.
   else {
      # delete new tempory file that was created
      @cmd_out = `/bin/rm $file_name 2>&1`;
   }
   
  
}     # end of "opened ok" block

# the file didn't open successfully
else {

   # get the error
   $file_error = $!;

   # print error message and exit
   &printEMsg("EMsgpreprpnodeAclsFileError",$file_name,$file_error);
   # delete new tempory file that was created
   @cmd_out = `/bin/rm $file_name 2>&1`;
   close (LOCKFH);
   exit(CRM_CLI_USER_ERROR);
}

}   # end update_acls_file


#--------------------------------------------------------------------#
# insert_in_array - Inserts an element into an array into the        #
#                   specified position.                              #
#                                                                    #
# Input:                                                             #
#   $line                    The line to insert.                     #
#   $where                   The line number to insert after         #
#   @in_array                The old array                           #
#                                                                    #
# Returns:                                                           #
#   @out_array               The new array                           #
#                                                                    #
#--------------------------------------------------------------------#
sub insert_in_array
{

my $line = shift @_;
my $where = shift @_;
my @in_array = @_;
my $i = 0;
my $j = 0;
my @out_array = ();

# copy the beginning
for ($i=0;$i <= $where; $i++) {
   $out_array[$i] = $in_array[$i];
}

# add new line
$out_array[$i] = $line;

# copy the rest
for ($i=$i+1;$i <= ($#in_array+1); $i++) {
   $out_array[$i] = $in_array[$i-1];
}

return (@out_array);
}   # end of insert_in_array


#--------------------------------------------------------------------#
# add_stanza_line - Adds the specified line into the specified       #
#                   stanza unless it's already there.  The line is   #
#                   added to the end of the stanza.                  #
#                                                                    #
# Input:                                                             #
#   $newline                 The line to add.                        #
#   $start                   Index where to start looking for $line. #
#   $stop                    Index where to stop looking for $line.  #
#                            ($start/$stop are inclusive)            #
#   @in_array                The current stanza array                #
#                                                                    #
# Returns:                                                           #
#   @out_array               The new stanza array                    #
#                                                                    #
#--------------------------------------------------------------------#
sub add_stanza_line
{

my $newline = shift @_;
my $start = shift @_;
my $stop = shift @_;
my @in_array = @_;
my @out_array = ();

my $stanza_line_found = $FALSE;
my $kk = 0;
my $inline = "";
my $newline_strip = "";

# set out_array to in_array
@out_array = @in_array;

# strip the new line we're looking for
# get rid of in line comments
$newline_strip = $newline;
$newline_strip =~ s/\/\/.*//g;
$newline_strip =~ s/#.*//g;

# get rid of all spaces
$newline_strip =~ s/\s//g;

# look for the line between $start and $stop (inclusive)
for (my $kk=$start;$kk<=$stop;$kk++) {
   $inline = $in_array[$kk];

   # get rid of in line comments
   $inline =~ s/\/\/.*//g;
   $inline =~ s/#.*//g;

   # get rid of all spaces 
   $inline =~ s/\s//g;

   # see if this is the stanza line we're looking for
   if ($inline eq $newline_strip) {
      $stanza_line_found = $TRUE;
   }
}  # end of for loop

# add the new stanza line if necessary
if (! $stanza_line_found) {
   @out_array = &insert_in_array($newline,$stop,@in_array);
}

return (@out_array);
}   # end of add_stanza_line


#--------------------------------------------------------------------#
# find_stanza_line - Looks for the specified line in the specified   #
#                   stanza.  If it's there, the line number (>=0) is #
#                   returned.                                        #
#                                                                    #
# Input:                                                             #
#   $findline                The line to find.                       #
#   $start                   Index where to start looking for $line. #
#   $stop                    Index where to stop looking for $line.  #
#                            ($start/$stop are inclusive)            #
#   @in_array                The current stanza array                #
#                                                                    #
# Returns:                                                           #
#   $linenum                 The line number between $start and      #
#                            $stop (inclusive) where the line was    #
#                            found.  If not found, -1 is returned.   #
#                                                                    #
#--------------------------------------------------------------------#
sub find_stanza_line
{

my $findline = shift @_;
my $start = shift @_;
my $stop = shift @_;
my @in_array = @_;

my $stanza_line_found = $FALSE;
my $kk = 0;
my $inline = "";
my $findline_strip = "";
my $line_num = 0;

# strip the new line we're looking for
# get rid of in line comments
$findline_strip = $findline;
$findline_strip =~ s/\/\/.*//g;
$findline_strip =~ s/#.*//g;

# get rid of all spaces
$findline_strip =~ s/\s//g;

# look for the line between $start and $stop (inclusive)
for (my $kk=$start;(($kk<=$stop) && (!$stanza_line_found));$kk++) {
   $inline = $in_array[$kk];

   # get rid of in line comments
   $inline =~ s/\/\/.*//g;
   $inline =~ s/#.*//g;

   # get rid of all spaces
   $inline =~ s/\s//g;

   # see if this is the stanza line we're looking for
   if ($inline eq $findline_strip) {
      $stanza_line_found = $TRUE;
      $linenum = $kk;
   }
}  # end of for loop

# return the line number if the line stanza was found
if ($stanza_line_found) { return ($linenum); }
# return -1 if it wasn't found
else { return (-1) }; 
}   # end of find_stanza_line


#--------------------------------------------------------------------#
# find_stanzas - Searched an array and forms a hash containing the   #
#                start and stop indexes for the stanzas.  The start  #
#                hash is the stanza name.  The end hash is ENDxxx    #
#                followed by the stanza name.                        #
#                                                                    #
# Input:                                                             #
#   @acl_array               acl array.                              #
#                                                                    #
# Returns:                                                           #
#   %acl_hash                acl hash.                               #
#                                                                    #
#--------------------------------------------------------------------#
sub find_stanzas
{

my @acl_array = @_;
my %acl_hash = ();                         # stanzas hash
my $acl_line = "";
my $stanza_found = $FALSE;
my $stanza_name = "";
my $junk = "";
my $i = 0;
my $possible_end_found = $FALSE;
my $possible_end = 0;

foreach $acl_line (@acl_array) {

   # see if it's the beginning of a staza
   if ( ! (($acl_line =~ /^#/) || ($acl_line =~ /^ /) || ($acl_line =~ /^$/)) ) {

       # it's the beginning of a stanza
       # mark the end of the last one, if necessary
       if ($stanza_found) {
           if ($possible_end_found) { $acl_hash{"ENDxxx".$stanza_name} = $possible_end;}
           else { $acl_hash{"ENDxxx".$stanza_name} = $i-1;}
       }

       # mark new stanza
       ($stanza_name, $junk) = split / /, $acl_line;
       $acl_hash{$stanza_name} = $i;
       $stanza_found = $TRUE;
       $possible_end_found = $FALSE;
   }

   # check it is a candidate to be the end of the stanza (skip comments for next stanza)
   else {
      # if it's a comment or a blank line, maybe it's really the start of a new stanza
      $acl_line =~ s/\s//g;
      if (($acl_line =~ /^#/) || ($acl_line =~ /^$/)) {
         if (!$possible_end_found) { 
            $possible_end = $i-1;
            $possible_end_found = $TRUE;
         }
      }
      # it's data
      else { $possible_end_found = $FALSE;}
   }
   
   $i++;

}  

# mark the end of the final stanza, if necessary
if ($stanza_found) {
    if ($possible_end_found) { $acl_hash{"ENDxxx".$stanza_name} = $possible_end;}
    else { $acl_hash{"ENDxxx".$stanza_name} = $i-1;}
}

return (%acl_hash);
}   # end of find_stanzas


#--------------------------------------------------------------------#
# remove_hostname_not_found - Scans the input varaible for error     #
#   messages thought to be from the hostname attribute not found     #
#   error.  This error os deleted from the input variable, and if    #
#   there are no other error, the rc variable is changed to 0.       #
#                                                                    #
# Parameters:                                                        #
#   $rc               Return code from lsrsrc-api.                   #
#   $delim            Delimiter for lsrsrc-api output.               #
#   @command_output   The output from the command.                   #
#                                                                    #
# Returns:                                                           #
#   @errorless_output The original @command_output array contents    #
#                     with the hostname not found errors removed.    #
#                                                                    #
# Global Variables:                                                  #
#--------------------------------------------------------------------#
sub remove_hostname_not_found
{
my $rc = shift @_;                      # get the return code
my $delim = shift @_;                   # get the delimiter
my @command_output = @_;                # command output to scan
my @new_output = ();                    # Hostname errors removed
my $line = "";                          # for each output line
my @err_msg = ();                       # split error message
my $num_err = 0;                        # number of errors found
my $num_host_err = 0;                   # number of hostname errors found

# scan each line for ERROR
foreach $line (@command_output) {

   # does it start with ERROR?
   if (!($line =~ /^ERROR.*/)) {

      # put it in the errorless array
      push @new_output, $line;
   }

   # so it's an error message
   else {
      $num_err++;

      # parse the message
      # if there's a rc=5, class is HostPublic, and there's "Hostname" in the 
      # message text, it's probably the Hostname not found error.
      # message format: ERROR::IBM.HostPublic::::::5::::0::lsrsrc-api: 2612-018 Attribute name 
      #                 Hostname is not a valid attribute name.
      @err_msg = split /$delim/, $line;
      if (  ($err_msg[1] =~ /^IBM.HostPublic/ )  &&
            ($err_msg[2] == 5 )  &&
            ($err_msg[5] =~ /Hostname/) ) {

            $num_host_err++;
         }

      else {
         # put it in the new output array
         push @new_output, $line;
      }
   
   }
}

# reset rc if needed
if ( ($num_err > 0)   &&  ($num_err == $num_host_err) ) {
   $rc = 0;
}

return ($rc, @new_output);
}   #  end of remove_hostname_not_found
